package com.cooqe.collection_snmp.snmpfinder;

import com.alibaba.fastjson.JSONObject;
import com.cooqe.collection_snmp.model.SnmpParameter;
import com.cooqe.collection_snmp.utils.SnmpUtil;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.VariableBinding;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @author 折戟沉沙铁未销
 * @version V1.0
 * @date 2023/11/16-2023
 * @Description: snmp walk 方式进行指标的采集。 snmpwalk 实际上一系列的 Get-Request 请求的组合
 */
@Component
@Slf4j
@SuppressWarnings("all")
public class SnmpWalkHandle {

    /**
     * snmp walk 操作，进行oid 指标值的获取
     * ----------------------------------------
     * {
     * "method": "walk",
     * "oids": "1.3.6.1.2.1.31.1.1.1.2:1.3.6.1.2.1.31.1.1.1.8"
     * }
     * ----------------------------------------
     */
    public static Map<String, List<String>> snmpWalk(JSONObject collectParamsObj, SnmpParameter snmpParameter, Target target, Snmp snmp) throws Exception {
        //获取参数 oid
        String oids = collectParamsObj.getString(SnmpUtil.OIDS);
        String[] oidArrays = oids.split(SnmpUtil.REGEX_SEGMENT);

        //定义返回结果
        Map<String, List<String>> resultMap = new HashMap<>();

        //遍历oid，walk 多个oid 。查询对应的指标值
        for (String oidVal : oidArrays) {

            //关于默认值的设置问题。如果是 OID 无效的情况下，则设置默认值。如果是因为超时等问题，设置默认值为 null
            int successType = SnmpUtil.SUCCESS;

            /**
             * xxx:zzz 这种拼接的形式，就是为了尽最大尝试而使用的。并不是对拼接的所有oid都进行一次采集请求。只是当上一个采集不到值的时候，才会使用冒号分隔符后面的
             * 目前这一块的逻辑参照 qz 的代码逻辑实现。
             * xxx:zzz  对于这种, 一个 oid字符串中使用 : 进行拼接的多个OID。
             * 在采集的时候，首先进行分割。 依次使用得到的单个 OID 进行 getNext 一些列操作。
             * 当第一个OID出现首次 getNext 就出错超时，或者首次 getNext 得到的结果就已经不是同一层的数据了。此时才会 break, 使用 : 后的 OID 再进行该指标的采集。
             * 如果 xxx:zzz 使用xxx 就已经取回了采集的数据，不会再使用 zzz 进行采集了。
             */
            String[] arrayOids = oidVal.split(SnmpUtil.REGEX_TEXT);
            for (String singleOid : arrayOids) {
                //定义 PDU 对象
                PDU requestPDU = (snmpParameter.getVersion() == SnmpConstants.version3) ? (new ScopedPDU()) : (new PDU());

                // 用于统计执行GetNext的操作次数。一般情况下至少两次操作才能判断操作正常结束。如果只有一次即结束，表明该oid无效。需要设置默认值
                int getNextCount = 0;

                // 请求OID 不符合校验条件
                if (!singleOid.matches("(\\d+\\.)+\\d+")) {
                    // 设置默认值,再有默认值的情况下
                    // 索引值个数,根据索引值得个数来设置默认值的个数
                    int indexSize = 1;
                    if (resultMap.get(SnmpUtil.SNMP_INDEX) != null) {
                        indexSize = resultMap.get(SnmpUtil.SNMP_INDEX).size();
                    }
                    List<String> defaultValues = new ArrayList<>(indexSize);
                    for (int i = 0; i < indexSize; i++) {
                        defaultValues.add(successType == 2 ? null : singleOid);
                    }
                    resultMap.put(oidVal, defaultValues);
                    continue;
                }

                //请求参数
                requestPDU.add(new VariableBinding(new OID(singleOid)));
                requestPDU.setType(PDU.GETNEXT);

                //初始化定义 finished 判断是否 getNext 结束
                boolean finished = false;

                //循环 getNext 实现 类似 snmpwalk 效果
                while (!finished) {
                    //发送请求
                    ResponseEvent responseEvent = snmp.send(requestPDU, target);
                    getNextCount++;

                    //请求成功，处理响应
                    if (responseEvent != null && responseEvent.getResponse() != null) {
                        //获取响应结果
                        PDU response = responseEvent.getResponse();
                        VariableBinding firstColumnVar = response.get(0);
                        finished = SnmpUtil.isEndWalk(firstColumnVar, requestPDU.get(0).getOid(), response);

                        //不需要 end, 说明本次getNext 的结果还在一层树
                        //不需要 end, 还需要下一步继续 getNext。(说明本次请求的结果还是需要数据)
                        if (!finished) {
                            // 获取响应结果
                            Vector<? extends VariableBinding> bindings = response.getVariableBindings();
                            // 解析响应结果
                            SnmpUtil.resolveSnmpReturnValue(resultMap, PDU.GETNEXT, bindings, singleOid, oidVal);
                            //重新设置。进行下一次的 getNext
                            requestPDU.setRequestID(new Integer32(0));
                            for (int i = 0; i < bindings.size(); i++) {
                                VariableBinding variableBinding = bindings.get(i);
                                requestPDU.set(i, variableBinding);
                            }
                        }
                        //说明使用该 oid 进行了一次 getNext 操作之后，就walk 结束了
                        //使用该 oid 进行了一次 getNext 操作之后并且请求是成功的，但是 不属于同一层 树了。 walk 结束了
                        else if (getNextCount == 1) {
                            // next一次即结束，oid无效
                            log.error("next一次即结束，oid无效");
                            successType = SnmpUtil.OID_INVALID;
                        }
                    }
                    // 请求失败，如: 超时
                    else {
                        log.error("根据该 oid 请求异常，超时或者其他原因等......");
                        successType = SnmpUtil.TIMEOUT;
                        finished = true;
                    }
                }
                //跳出循环
                if (successType == SnmpUtil.SUCCESS) { break;}
            }
        }

        //返回
        return resultMap;
    }
}